namespace ts {
    /* @internal */
    export function trace(host: ModuleResolutionHost, message: DiagnosticMessage, ...args: any[]): void;
    export function trace(host: ModuleResolutionHost): void {
        host.trace!(formatMessage.apply(undefined, arguments));
    }

    /* @internal */
    export function isTraceEnabled(host: ModuleResolutionHost): boolean {
        return host.trace !== undefined;
    }

    /** Array that is only intended to be pushed to, never read. */
    /* @internal */
    export interface Push<T> {
        push(value: T): void;
    }

    interface ModuleResolutionState {
        host: ModuleResolutionHost;
        compilerOptions: CompilerOptions;
        traceEnabled: boolean;
        failedLookupLocations: Push<string>;
    }

    let typeScriptVersion: Version | undefined;

    /* @internal */
    export function getPackageJsonTypesVersionsPaths(typesVersions: MapLike<MapLike<string[]>>) {
        if (!typeScriptVersion) typeScriptVersion = new Version(version);

        for (const key in typesVersions) {
            if (!hasProperty(typesVersions, key)) continue;

            const keyRange = VersionRange.tryParse(key);
            if (keyRange === undefined) {
                continue;
            }

            // return the first entry whose range matches the current compiler version.
            if (keyRange.test(typeScriptVersion)) {
                return { version: key, paths: typesVersions[key] };
            }
        }
    }

    /**
     * Cached module resolutions per containing directory.
     * This assumes that any module id will have the same resolution for sibling files located in the same folder.
     */
    export interface ModuleResolutionCache extends NonRelativeModuleNameResolutionCache {
        getOrCreateCacheForDirectory(directoryName: string): Map<ResolvedModuleWithFailedLookupLocations>;
        /*@internal*/ directoryToModuleNameMap: CacheWithRedirects<Map<ResolvedModuleWithFailedLookupLocations>>;
    }

    /**
     * Stored map from non-relative module name to a table: directory -> result of module lookup in this directory
     * We support only non-relative module names because resolution of relative module names is usually more deterministic and thus less expensive.
     */
    export interface NonRelativeModuleNameResolutionCache {
        getOrCreateCacheForModuleName(nonRelativeModuleName: string): PerModuleNameCache;
        /*@internal*/ moduleNameToDirectoryMap: CacheWithRedirects<PerModuleNameCache>;
    }

    export interface PerModuleNameCache {
        get(directory: string): ResolvedModuleWithFailedLookupLocations | undefined;
        set(directory: string, result: ResolvedModuleWithFailedLookupLocations): void;
    }

    export function createModuleResolutionCache(currentDirectory: string, getCanonicalFileName: (s: string) => string): ModuleResolutionCache {
        return createModuleResolutionCacheWithMaps(
            createCacheWithRedirects(),
            createCacheWithRedirects(),
            currentDirectory,
            getCanonicalFileName
        );
    }

    /*@internal*/
    export interface CacheWithRedirects<T> {
        ownMap: Map<T>;
        getOrCreateMapOfCacheRedirects(): Map<T>;
        clear(): void;
        setOwnMap(newOwnMap: Map<T>): void;
    }

    /*@internal*/
    export function createCacheWithRedirects<T>(): CacheWithRedirects<T> {
        let ownMap: Map<T> = createMap();
        return {
            ownMap,
            getOrCreateMapOfCacheRedirects,
            clear,
            setOwnMap
        };

        function setOwnMap(newOwnMap: Map<T>) {
            ownMap = newOwnMap;
        }

        function getOrCreateMapOfCacheRedirects() {
            return ownMap;
        }

        function clear() {
            ownMap.clear();
        }
    }

    /*@internal*/
    export function createModuleResolutionCacheWithMaps(
        directoryToModuleNameMap: CacheWithRedirects<Map<ResolvedModuleWithFailedLookupLocations>>,
        moduleNameToDirectoryMap: CacheWithRedirects<PerModuleNameCache>,
        currentDirectory: string,
        getCanonicalFileName: GetCanonicalFileName): ModuleResolutionCache {

        return { getOrCreateCacheForDirectory, getOrCreateCacheForModuleName, directoryToModuleNameMap, moduleNameToDirectoryMap };

        function getOrCreateCacheForDirectory(directoryName: string) {
            const path = toPath(directoryName, currentDirectory, getCanonicalFileName);
            return getOrCreateCache<Map<ResolvedModuleWithFailedLookupLocations>>(directoryToModuleNameMap, path, createMap);
        }

        function getOrCreateCacheForModuleName(nonRelativeModuleName: string): PerModuleNameCache {
            Debug.assert(!isExternalModuleNameRelative(nonRelativeModuleName));
            return getOrCreateCache(moduleNameToDirectoryMap, nonRelativeModuleName, createPerModuleNameCache);
        }

        function getOrCreateCache<T>(cacheWithRedirects: CacheWithRedirects<T>, key: string, create: () => T): T {
            const cache = cacheWithRedirects.getOrCreateMapOfCacheRedirects();
            let result = cache.get(key);
            if (!result) {
                result = create();
                cache.set(key, result);
            }
            return result;
        }

        function createPerModuleNameCache(): PerModuleNameCache {
            const directoryPathMap = createMap<ResolvedModuleWithFailedLookupLocations>();

            return { get, set };

            function get(directory: string): ResolvedModuleWithFailedLookupLocations | undefined {
                return directoryPathMap.get(toPath(directory, currentDirectory, getCanonicalFileName));
            }

            /**
             * At first this function add entry directory -> module resolution result to the table.
             * Then it computes the set of parent folders for 'directory' that should have the same module resolution result
             * and for every parent folder in set it adds entry: parent -> module resolution. .
             * Lets say we first directory name: /a/b/c/d/e and resolution result is: /a/b/bar.ts.
             * Set of parent folders that should have the same result will be:
             * [
             *     /a/b/c/d, /a/b/c, /a/b
             * ]
             * this means that request for module resolution from file in any of these folder will be immediately found in cache.
             */
            function set(directory: string, result: ResolvedModuleWithFailedLookupLocations): void {
                const path = toPath(directory, currentDirectory, getCanonicalFileName);
                // if entry is already in cache do nothing
                if (directoryPathMap.has(path)) {
                    return;
                }
                directoryPathMap.set(path, result);

                const resolvedFileName = result.模块目录路径 && result.模块目录路径.解析的模块目录路径;
                // find common prefix between directory and resolved file name
                // this common prefix should be the shortest path that has the same resolution
                // directory: /a/b/c/d/e
                // resolvedFileName: /a/b/foo.d.ts
                // commonPrefix: /a/b
                // for failed lookups cache the result for every directory up to root
                const commonPrefix = resolvedFileName && getCommonPrefix(path, resolvedFileName);
                let current = path;
                while (current !== commonPrefix) {
                    const parent = getDirectoryPath(current);
                    if (parent === current || directoryPathMap.has(parent)) {
                        break;
                    }
                    directoryPathMap.set(parent, result);
                    current = parent;
                }
            }

            function getCommonPrefix(directory: Path, resolution: string) {
                const resolutionDirectory = toPath(getDirectoryPath(resolution), currentDirectory, getCanonicalFileName);

                // find first position where directory and resolution differs
                let i = 0;
                const limit = Math.min(directory.length, resolutionDirectory.length);
                while (i < limit && directory.charCodeAt(i) === resolutionDirectory.charCodeAt(i)) {
                    i++;
                }
                if (i === directory.length && (resolutionDirectory.length === i || resolutionDirectory[i] === directorySeparator)) {
                    return directory;
                }
                const rootLength = getRootLength(directory);
                if (i < rootLength) {
                    return undefined;
                }
                const sep = directory.lastIndexOf(directorySeparator, i - 1);
                if (sep === -1) {
                    return undefined;
                }
                return directory.substr(0, Math.max(sep, rootLength));
            }
        }
    }

    /*@internal*/
    export const nodeModulesPathPart = "/模块/";
    /*@internal*/
    export function pathContainsNodeModules(path: string): boolean {
        return stringContains(path, nodeModulesPathPart);
    }


    export function resolveModuleNameFromCache(moduleName: string, containingFile: string, cache: ModuleResolutionCache): ResolvedModuleWithFailedLookupLocations | undefined {
        const containingDirectory = getDirectoryPath(containingFile);
        const perFolderCache = cache && cache.getOrCreateCacheForDirectory(containingDirectory);
        return perFolderCache && perFolderCache.get(moduleName);
    }

    export function resolveModuleName(moduleName: string, containingFile: string, host: ModuleResolutionHost, cache?: ModuleResolutionCache): ResolvedModuleWithFailedLookupLocations {
        if (!moduleName) return {};
        const traceEnabled = isTraceEnabled(host);
        if (traceEnabled) {
            trace(host, Diagnostics.Resolving_module_0_from_1, moduleName, containingFile);
        }
        const containingDirectory = getDirectoryPath(containingFile);
        const perFolderCache = cache && cache.getOrCreateCacheForDirectory(containingDirectory);
        let result = perFolderCache && perFolderCache.get(moduleName);
        const 配置文件名称 = "klang.mod.json";
        let 当前配置文件目录 = normalizePath(containingDirectory);
        let 当前配置文件名称 = normalizePath(combinePaths(当前配置文件目录, 配置文件名称));
        while (!host.fileExists(当前配置文件名称) && getRootLength(当前配置文件目录) !== 当前配置文件目录.length) {
            当前配置文件目录 = getDirectoryPath(当前配置文件目录);
            当前配置文件名称 = normalizePath(combinePaths(当前配置文件目录, 配置文件名称));
        }
        if (!host.fileExists(当前配置文件名称) && traceEnabled) {
            trace(host, Diagnostics.Resolving_module_0_from_1, moduleName, containingFile);
        }
        const 当前项目配置 = 当前配置文件名称;
        const 当前源码目录 = combinePaths(getDirectoryPath(当前配置文件名称), "源码");
        const 当前模块目录 = host.当前模块目录!;         
        const 全局模块目录 = host.全局模块目录!;     
        const 依赖模块目录 = createMap<string>();
        const 依赖 = (<KlangModConfig>readJson(当前项目配置!, host)).依赖 || {};
        for (const k in 依赖) {
            const 版本号 = 依赖[k]           
            const 键 = last(normalizePathAndParts(normalizePath(k)).parts);
            if (!依赖模块目录.has(键)) {
                依赖模块目录.set(正规化模块名称(键), normalizePath(combinePaths(k, 版本号)));
            }
        }
        if (result) {
            if (traceEnabled) {
                trace(host, Diagnostics.Resolution_for_module_0_was_found_in_cache_from_location_1, moduleName, containingDirectory);
            }
        }
        else {
            获取结果(当前源码目录, 模块导入位置种类_.无_);
            if (!result) {
                获取结果(当前模块目录, 模块导入位置种类_.外部支持库_, 依赖模块目录);
            }
            if (!result) {
                获取结果(全局模块目录, 模块导入位置种类_.全局支持库_, 依赖模块目录);
            }
            if (perFolderCache && result) {
                perFolderCache.set(moduleName, result);
                const 每个模块名缓存 = cache!.getOrCreateCacheForDirectory(moduleName);
                if (每个模块名缓存) {
                    每个模块名缓存.set(containingDirectory, result);
                }
            }
        }

        if (traceEnabled) {
            if (result && result.模块目录路径) {
                trace(host, Diagnostics.Module_name_0_was_successfully_resolved_to_1, moduleName, result!.模块目录路径.解析的模块目录路径);
            }
            else {
                trace(host, Diagnostics.Module_name_0_was_not_resolved, moduleName);
            }
        }

        return result || {};

        function 获取结果(基础目录: string, 种类: 模块导入位置种类_, 依赖?: Map<string>) {
            let 模块目录: string = "";
            if (!依赖) {
                模块目录 = normalizePath(combinePaths(基础目录!, moduleName));
            }
            else if (依赖.has(moduleName)) {
                模块目录 = normalizePath(combinePaths(基础目录!, 依赖.get(moduleName)));
            }
            if (directoryProbablyExists(模块目录, host)) {
                result = {
                    模块目录路径: {
                        解析的模块目录路径: 模块目录,
                        是外部支持库导入: 种类
                    }
                }
                return;
            }
            模块目录 = normalizePath(combinePaths(host.全局模块目录!, moduleName));
            if(directoryProbablyExists(模块目录, host)) {
                result = {
                    模块目录路径: {
                        解析的模块目录路径: 模块目录,
                        是外部支持库导入: 模块导入位置种类_.全局支持库_
                    }
                }
                return;
            }
        }
    }

    function 包含KlangId(r: 解析的_ | undefined): 解析的_ | undefined {
        return r ? { 路径: r.路径 } : undefined;
    }

    /** Result of trying to resolve a module. */
    interface 解析的_ {
        路径: string
    }

    function 创建解析模块包括失败查找位置(解析的: 解析的_ | undefined): ResolvedModuleWithFailedLookupLocations {
        return {
            模块目录路径: 解析的 && { 解析的模块目录路径: 解析的.路径! },
        };
    }

    function 路径转为KlangJson(目录: string): string {
        return combinePaths(目录, 'klang.mod.json');
    }

    function 获取Klang包信息(模块目录: string, 仅记录失败的: boolean, 状态: ModuleResolutionState, ): { 找到: boolean, 包内容: KlangJson_ | undefined, 包Id: PackageId | undefined } {
        const { host, traceEnabled } = 状态;
        const 目录存在 = !仅记录失败的 && directoryProbablyExists(模块目录, host);
        const klang包路径 = 路径转为KlangJson(模块目录);

        if (目录存在 && host.fileExists(klang包路径)) {
            const klang包内容 = readJson(klang包路径, host) as KlangJson_;

            const klangId: PackageId = typeof klang包内容.名称 === 'string' && typeof klang包内容.版本 === 'string'
                ? { name: klang包内容.名称, version: klang包内容.版本 }
                : undefined!;
            if (traceEnabled) {
                if (klangId) {
                    trace(host, Diagnostics.Found_package_json_at_0_Package_ID_is_1, klang包路径, klangId.name);
                }
                else {
                    trace(host, Diagnostics.Found_package_json_at_0, klang包路径);
                }
            }
            return { 找到: true, 包内容: klang包内容, 包Id: klangId };
        }
        else {
            if (目录存在 && traceEnabled) {
                trace(host, Diagnostics.File_0_does_not_exist, klang包路径);
            }
            return { 找到: false, 包内容: undefined, 包Id: undefined };
        }
    }

    function 加载模块从文件(候选: string, 仅记录失败: boolean, 状态: ModuleResolutionState): 解析的_ | undefined {
        if (!仅记录失败) {
            // check if containing folder exists - if it doesn't then just record failures for all supported extensions without disk probing
            const 目录 = getDirectoryPath(候选);
            if (目录) {
                仅记录失败 = !directoryProbablyExists(目录, 状态.host);
            }
        }
        const 路径 = 尝试从文件(候选, 仅记录失败, 状态);
        return 路径 ? { 路径 } : undefined;
    }

    function 尝试从文件(目录: string, 仅记录失败的: boolean, 状态: ModuleResolutionState): string | undefined {
        if (!仅记录失败的) {
            if (状态.host.directoryExists!(目录)) {
                if (状态.traceEnabled) {
                    trace(状态.host, Diagnostics.File_0_exist_use_it_as_a_name_resolution_result, 目录);
                }
                return 目录;
            }
            else {
                if (状态.traceEnabled) {
                    trace(状态.host, Diagnostics.File_0_does_not_exist, 目录);
                }
            }
        }
        return undefined;
    }

    function 加载模块从目录平台(候选: string, 仅记录失败的: boolean, 状态: ModuleResolutionState, klang包内容: KlangJson_ | undefined): 解析的_ | undefined {
        const 从包内容 = klang包内容 && 加载模块从Klang包(klang包内容, 候选, 状态);
        if (从包内容) {
            return 从包内容;
        }
        const 目录存在 = !仅记录失败的 && directoryProbablyExists(候选, 状态.host);
        return 加载模块从文件(候选, !目录存在, 状态);
    }

    /** Reads from 'main' or '类型组'/'typings' depending on `extensions`. */
    function 尝试读KlangJson字段(json内容: KlangJson_, 基目录: string, 状态: ModuleResolutionState): string | undefined {
        return 尝试读字段('名称');

        function 尝试读字段(字段名: '名称'): string | undefined {
            if (!hasProperty(json内容, 字段名)) {
                if (状态.traceEnabled) {
                    trace(状态.host, Diagnostics.package_json_does_not_have_a_0_field, 字段名);
                }
                return;
            }

            const 文件名 = json内容[字段名];
            if (!isString(文件名)) {
                if (状态.traceEnabled) {
                    trace(状态.host, Diagnostics.Expected_type_of_0_field_in_package_json_to_be_string_got_1, 字段名, typeof 文件名);
                }
                return;
            }

            const 路径 = normalizePath(combinePaths(基目录, 文件名));
            if (状态.traceEnabled) {
                trace(状态.host, Diagnostics.package_json_has_0_field_1_that_references_2, 字段名, 文件名, 路径);
            }
            return 路径;
        }
    }

    function 按相对名加载模块(候选: string, 仅记录失败的: boolean, 状态: ModuleResolutionState, 考虑包内容: boolean): 解析的_ | undefined {
        if (状态.traceEnabled) {
            trace(状态.host, Diagnostics.Loading_module_as_file_Slash_folder_candidate_module_location_0_target_file_type_1, 候选);
        }
        if (!hasTrailingDirectorySeparator(候选)) {
            if (!仅记录失败的) {
                const 候选的父级 = getDirectoryPath(候选);
                if (!directoryProbablyExists(候选的父级, 状态.host)) {
                    if (状态.traceEnabled) {
                        trace(状态.host, Diagnostics.Directory_0_does_not_exist_skipping_all_lookups_in_it, 候选的父级);
                    }
                    仅记录失败的 = true;
                }
            }
            const 解析从文件 = 加载模块从文件(候选, 仅记录失败的, 状态);
            if (解析从文件) {
                return 包含KlangId(解析从文件);
            }
        }
        if (!仅记录失败的) {
            const 候选存在 = directoryProbablyExists(候选, 状态.host);
            if (!候选存在) {
                if (状态.traceEnabled) {
                    trace(状态.host, Diagnostics.Directory_0_does_not_exist_skipping_all_lookups_in_it, 候选);
                }
                仅记录失败的 = true;
            }
        }
        return 加载模块从目录(候选, 仅记录失败的, 状态, 考虑包内容);
    }

    function 加载模块从目录(候选: string, 仅记录失败的: boolean, 状态: ModuleResolutionState, 考虑KlangJson = true) {
        const { 包内容 } = 考虑KlangJson
            ? 获取Klang包信息(候选, 仅记录失败的, 状态)
            : { 包内容: undefined };
        return 包含KlangId(加载模块从目录平台(候选, 仅记录失败的, 状态, 包内容));
    }

    function 加载模块从Klang包(json内容: KlangJson_, 候选: string, 状态: ModuleResolutionState): 解析的_ | undefined {
        const 文件 = 尝试读KlangJson字段(json内容, 候选, 状态);
        if (!文件) {
            return undefined;
        }

        const 仅记录失败 = !directoryProbablyExists(getDirectoryPath(文件), 状态.host);
        const 路径 = 尝试从文件(文件, 仅记录失败, 状态);
        if (路径) {
            return { 路径 };
        }
        if (状态.traceEnabled) {
            trace(状态.host, Diagnostics.File_0_has_an_unsupported_extension_so_skipping_it, 路径);
        }
        // Don't do package.json lookup recursively, because 节点_.js' package lookup doesn't.
        const 结果 = 按相对名加载模块(文件, 仅记录失败, 状态, /*considerPackageJson*/ false);
        if (结果) {
            return { 路径: 结果.路径 };
        }
    }

    function 加载模块从模块文件夹(模块名称: string, 模块文件夹: string, 模块文件夹存在: boolean, 状态: ModuleResolutionState): 解析的_ | undefined {
        const 候选 = normalizePath(combinePaths(模块文件夹, 模块名称));
        // First look for a nested package.json, as in `node_modules/foo/bar/package.json`.
        let klang包内容: KlangJson_ | undefined;

        const klang包信息 = 获取Klang包信息(候选, /*onlyRecordFailures*/ !模块文件夹存在, 状态);
        if (klang包信息.找到) {
            ({ 包内容: klang包内容 } = klang包信息);
        }
        const 路径和扩展名 = 加载模块从文件(候选, !模块文件夹存在, 状态) || 加载模块从目录平台(候选, !模块文件夹存在, 状态, klang包内容);
        return 包含KlangId(路径和扩展名);
    }

    /** Load a module from a single node_modules directory, but not from any ancestors' node_modules directories. */
    function 加载模块从模块一级(模块名称: string, 目录: string, 状态: ModuleResolutionState, ): 解析的_ | undefined {
        const 模块文件夹 = combinePaths(目录, '模块');
        const 模块文件夹存在 = directoryProbablyExists(模块文件夹, 状态.host);
        if (!模块文件夹存在 && 状态.traceEnabled) {
            trace(状态.host, Diagnostics.Directory_0_does_not_exist_skipping_all_lookups_in_it, 模块文件夹);
        }

        return 加载模块从模块文件夹(模块名称, 模块文件夹, 模块文件夹存在, 状态);
    }

    /**
     * A host may load a module from a global cache of typings.
     * This is the minumum code needed to expose that functionality; the rest is in the host.
     */
    /* @internal */
    export function loadModuleFromGlobalCache(moduleName: string, projectName: string | undefined, compilerOptions: CompilerOptions, host: ModuleResolutionHost, globalCache: string): ResolvedModuleWithFailedLookupLocations {
        const traceEnabled = isTraceEnabled(host);
        if (traceEnabled) {
            trace(host, Diagnostics.Auto_discovery_for_typings_is_enabled_in_project_0_Running_extra_resolution_pass_for_module_1_using_cache_location_2, projectName, moduleName, globalCache);
        }
        const failedLookupLocations: string[] = [];
        const state: ModuleResolutionState = { compilerOptions, host, traceEnabled, failedLookupLocations };
        const resolved = 加载模块从模块一级(moduleName, globalCache, state);
        return 创建解析模块包括失败查找位置(resolved);
    }
}
